#include <glib-2.0/glib/glist.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib-2.0/glib/gtypes.h>
#include <errno.h>
#include "superpeer.h"
#include "structures.h"
#include "glist_ts.h"

extern GList* fileRepresentation;
extern GList* connectedPeer;
extern GStaticRWLock fileRepresentationLock;
extern GStaticRWLock connectedPeerLock;

// funzione di confronto per la ricerca di un elemento nella glist per le strutture FILE_IN_SUPER_PEER
gint compare_FILE_IN_SUPER_PEER(gconstpointer a, gconstpointer b)
{
    struct FILE_IN_SUPER_PEER *fisp_a = (struct FILE_IN_SUPER_PEER*) a;
    struct FILE_IN_SUPER_PEER *fisp_b = (struct FILE_IN_SUPER_PEER*) b;
    return (gint)strcmp(fisp_a->ID, fisp_b->ID);
}

// funzione di confronto per la ricerca di un elemento nella glist per le strutture CHUNK_IN_SUPER_PEER
gint compare_CHUNK_IN_SUPER_PEER(gconstpointer a, gconstpointer b)
{
    struct CHUNK_IN_SUPER_PEER *cisp_a = (struct CHUNK_IN_SUPER_PEER*) a;
    struct CHUNK_IN_SUPER_PEER *cisp_b = (struct CHUNK_IN_SUPER_PEER*) b;
    return (gint) strcmp(cisp_a->ID, cisp_b->ID );
}

gint compare_PEER(gconstpointer a, gconstpointer b)
{
    //ritorna negativo se a è minore di b
    //ritorna zero se sono uguali
    //ritorna maggiore di zero se b è maggiore di a
    struct PEER *pa = (struct PEER *)a;
    struct PEER *pb = (struct PEER *)b;
    if(strcmp(pa->addr,pb->addr)==0){
        if(pa->port==pb->port){
            return 0;
        }
        else
            return -1;
    }
    else return 1;
}

// stampa la lista dei peer connessi
int print_connected_peer(GList *connectedPeer)
{
    guint n = g_list_length((GList*) connectedPeer);
    guint i;
    for(i=0;i<n;i++)
    {
        struct PEER *tmp = (struct PEER*) g_list_nth_data((GList*) connectedPeer, i);
        printf("peer connesso #%d:\n",i);
        printf("IP = %s\n", tmp->addr);
        printf("PORTA = %d\n", tmp->port);
        printf("DISPONIBILITA = %f\n", tmp->ava);
        printf("CARICO = %d\n", tmp->load);
    }
    return 0;
}

// stampa la lista dei possessori
int print_possessors_list(GList *poss)
{
    guint n = g_list_length((GList*) poss);
    guint i;
    for(i=0;i<n;i++)
    {
        struct PEER *tmp = (struct PEER*) g_list_nth_data((GList*) poss, i);
        printf("peer possessore #%d:\n",i);
        printf("IP = %s\n", tmp->addr);
        printf("PORTA = %d\n", tmp->port);
        printf("DISPONIBILITA = %f\n", tmp->ava);
        printf("CARICO = %d\n", tmp->load);
    }
    return 0;
}

// stampa la lista dei chunk
int print_CHUNK_IN_SUPERPEER_list(GList *list)
{
    guint n = g_list_length((GList*) list);
    guint i;
    for(i=0;i<n;i++)
    {
        struct CHUNK_IN_SUPER_PEER* tmp = (struct CHUNK_IN_SUPER_PEER*) g_list_nth_data((GList*) list, i);
        printf("chunk #%d\n",i);
        printf("hash = %s\n", tmp->ID);
        printf("posizione nel file = %d\n", tmp->positionInFile);
        printf("dimensione = %d\n", tmp->size);
        print_possessors_list(tmp->partialPossessorsPeers);

    }
    return 0;
}

// stampa la lista dei file
int print_FILE_IN_SUPER_PEER_list(GList *list)
{
    guint n = g_list_length((GList*) list);
    guint i;
    for(i=0;i<n;i++)
    {
        struct FILE_IN_SUPER_PEER *tmp = (struct FILE_IN_SUPER_PEER*) g_list_nth_data((GList*) list, i);
        printf("file #%d\n",i);
        printf("nome file = %s\n",tmp->name);
        printf("hash del file = %s\n",tmp->ID);
        printf("numero chunk = %d\n",tmp->chunksNumber);
        print_CHUNK_IN_SUPERPEER_list(tmp->chunkInSuperPeer);
        print_possessors_list(tmp->completePossessorsPeer);
    }
}

//inserisce nel sistema un nuovo peer in modo atomico
int insertNewPeer(struct PEER* newPeer, int numCompleteFile, char* hashCompleteFiles , int numChunkLite,
        struct chunkLight* chunksLite)
{
    // puntatore a funzione per il confronto dei peer
    gint(*compare_PEER_function) (gconstpointer a, gconstpointer b);
    compare_PEER_function = compare_PEER;
    // puntatore al vettore dei file
    char*hashCompleteFilesTmp = hashCompleteFiles;
    // puntatore al vettore dei chunk lite
    struct chunkLight* chunksLiteTmp = chunksLite;
    //controllo che il peer non sia già connesso
    GList* finded = g_list_find_custom_ts((GList*) connectedPeer, (gpointer) newPeer, compare_PEER_function, &connectedPeerLock);
    if(finded != NULL)
    {
        printf("il peer è già connesso e non può effettuare la JOIN\n");
        return -1;
    }
    //inserimento del peer nella lista dei peer connessi
    connectedPeer = g_list_insert_sorted_ts((GList*) connectedPeer, (gpointer) newPeer, compare_PEER_function, &connectedPeerLock);
    // inserimento del peer nella lista dei possessori completi dei file
    int i;
    for(i=0;i<numCompleteFile;i++)
    {
         //lista per la ricerca
        GList* fileFinded;
        // creazione di una struct finta per il confronto
        struct FILE_IN_SUPER_PEER *fisp_fake = calloc(1, sizeof (struct FILE_IN_SUPER_PEER));
        //metto l' hash nella struttuta finta
        strncpy(fisp_fake->ID, hashCompleteFilesTmp, 32);
        // creo un puntatore alla funzione
        gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
        compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
        fileFinded = g_list_find_custom_ts((GList*) fileRepresentation, fisp_fake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
        if (fileFinded != NULL)
        {
            // puntatore a new peer
            struct PEER *newPeerPtr = newPeer;
            //appendo alla lista dei possessori del file completo il puntatore al peer
            struct FILE_IN_SUPER_PEER *fisp_tmp = (struct FILE_IN_SUPER_PEER*) g_list_nth_data(fileFinded, 0);
            //appendo il peer alla lista
            fisp_tmp->completePossessorsPeer = g_list_append_ts(fisp_tmp->completePossessorsPeer, newPeerPtr, &fisp_tmp->completePossessorLock);
        }
        else
        {
            //ordina la cancellazione del file
            printf("ordinata la cancellazione del file\n");
            struct peerLight * cl1 = calloc(1,sizeof(struct peerLight));
            strcpy(cl1->addr,newPeer->addr);
            cl1->port=newPeer->port;
            request_delete_file(cl1,1,fisp_fake->ID);
        }
        //libero la memoria occupata da fisp_fake //da un errore su questa free
        free(fisp_fake);
        //incremento il puntatore
        hashCompleteFilesTmp+=32;
    }
    //inserimento dei cunk
    for(i=0;i<numChunkLite;i++)
    {
         //lista dei file trovati (massimo 1)
        GList* fileFinded;
         //lista dei chunk trovati (massimo 1)
        GList *chunkFinded;
        // creazione di una struct FILE_IN_SUPER_PEER finta per il confronto
        struct FILE_IN_SUPER_PEER *fisp_fake = calloc(1, sizeof (struct FILE_IN_SUPER_PEER));
        // creazione di una struct CHUNK_IN_SUPER_PEER finta per il confronto
        struct CHUNK_IN_SUPER_PEER *cisp_fake = calloc(1, sizeof (struct CHUNK_IN_SUPER_PEER));
        //metto l' hash nella struttuta finta
        strcpy(fisp_fake->ID, chunksLiteTmp->IDF);
        // creo un puntatore alla funzione
        gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
        compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
        fileFinded = g_list_find_custom_ts((GList*) fileRepresentation, fisp_fake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
        if (fileFinded != NULL)
        {
            //puntatore temporaneo al file
            struct FILE_IN_SUPER_PEER *fisp_tmp = (struct FILE_IN_SUPER_PEER*) g_list_nth_data(fileFinded, 0);
            // creo un puntatore alla funzione per la comparazione tra i CHUNK_IN_SUPER_PEER
            gint(*compare_CHUNK_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
            compare_CHUNK_IN_SUPER_PEER_function = compare_CHUNK_IN_SUPER_PEER;
            //inserisco nella struttura CHUNK_IN_SUPER_PEER fake l' hash del chunk
            strcpy(cisp_fake->ID, chunksLiteTmp->ID);
            chunkFinded = g_list_find_custom_ts((GList*)fisp_tmp->chunkInSuperPeer, cisp_fake, compare_CHUNK_IN_SUPER_PEER_function, &fisp_tmp->chunkListLock);
            if (chunkFinded != NULL)
            {
                // puntatore alla struttura CHUNK_IN_SUPER_PEER
                struct CHUNK_IN_SUPER_PEER *cispTmp = (struct CHUNK_IN_SUPER_PEER*) g_list_nth_data(chunkFinded, 0);
                // puntatore a new peer
                struct PEER *newPeerPtr = newPeer;
                //appendo il puntatore al peer connesso alla lista dei possessori parziali
                cispTmp->partialPossessorsPeers = g_list_append_ts(cispTmp->partialPossessorsPeers, newPeerPtr, &cispTmp->partialPossessorLock);
            }
            else
            {
                printf("errore chunk non trovato\n");
                return -2;
            }
        }
        else
        {
            //ordina la cancellazione del file
            printf("ordinata la cancellazione del file\n");
            struct peerLight * cl2 = calloc(1,sizeof(struct peerLight));
            strcpy(cl2->addr,newPeer->addr);
            cl2->port=newPeer->port;
            request_delete_file(cl2,1,fisp_fake->ID);
        }
        //libero la memoria occupata da fisp_fake
        free(fisp_fake);
        //libero la memoria occupata da cisp_fake
        free(cisp_fake);
        //incremento il puntatore
        chunksLiteTmp++;
    }
    return 0;
}



// rimuove un elemento dalla lista senza fare free
GList* g_list_remove_custom(GList* list,  gconstpointer data, GCompareFunc func)
{
    GList *find = g_list_find_custom(list, data, func);
    if(find != NULL)
    {
        list = g_list_remove_link(list, find);
    }
    return list;
}

// rimuove un elemento dalla lista ed esegue la free
GList* g_list_delete_custom(GList* list, gconstpointer data, GCompareFunc func)
{
    GList *find = g_list_find_custom(list, data, func);
    if(find != NULL)
    {
        list = g_list_delete_link(list, find);
    }
    return list;
}


int removePeer(struct PEER* peerToRemove)
{
    //numero file
    gint fileNumber;
    // numero chunk del file
    gint chunkNumber;
    // struttura FILE_IN_SUPER_PEER temporanea
    struct FILE_IN_SUPER_PEER * fispTmp;
    //struttura CHUNK_IN_SUPER_PEER temporanea
    struct CHUNK_IN_SUPER_PEER *cispTmp;
    //lista per la ricerca
    GList* find;
    // puntatore alla funzione per la comparazione del peer
    gint (*compare_PEER_function) (gconstpointer a, gconstpointer b);
    compare_PEER_function = compare_PEER;
    //ricerca nella lista del peer
    find = g_list_find_custom_ts(connectedPeer, peerToRemove, compare_PEER_function, &connectedPeerLock);
    //controllo esistenza del peer
    if(find == NULL)
    {
        printf("il peer non è connesso quindi la LEAVE non può essere eseguita\n");
        return -1;
    }


    //rimozione del peer dalla lista dei FILE_IN_SUPER_PEER
    fileNumber = g_list_length_ts(fileRepresentation, &fileRepresentationLock);
    guint i;
    for(i=0;i<fileNumber;i++)
    {
        fispTmp = (struct FILE_IN_SUPER_PEER*) g_list_get_element_at_ts(fileRepresentation,i, &fileRepresentationLock);
        fispTmp->completePossessorsPeer = g_list_remove_custom_ts(fispTmp->completePossessorsPeer, peerToRemove,
                compare_PEER_function, &fispTmp->completePossessorLock);

        chunkNumber = g_list_length_ts(fispTmp->chunkInSuperPeer, &fispTmp->chunkListLock);
        guint j;
        for(j=0;j<chunkNumber;j++)
        {
            cispTmp = (struct CHUNK_IN_SUPER_PEER*) g_list_get_element_at_ts(fispTmp->chunkInSuperPeer, j, &fispTmp->chunkListLock);
            cispTmp->partialPossessorsPeers = g_list_remove_custom_ts(cispTmp->partialPossessorsPeers, peerToRemove,
                    compare_PEER_function, &cispTmp->partialPossessorLock);
        }
    }
    //rimozione del peer dalla lista dei peer connessi
    connectedPeer = g_list_remove_custom_ts(connectedPeer, peerToRemove, compare_PEER_function, &connectedPeerLock);
    return 0;
}

// suppongo che i chunk siano ordinati per posizione nel file
int insertNewFile(struct FILE_IN_SUPER_PEER* newFile, struct chunk* chunkArray, int chunkNumber, int peerLoad, float peerAva, char* peerIP, int peerPort){
    struct chunk* chunkArrayPtr = chunkArray;
    struct PEER* peerFake = calloc(1, sizeof(struct PEER));
    strcpy(peerFake->addr, peerIP);
    peerFake->port = peerPort;
    gint(*compare_PEER_function) (gconstpointer a, gconstpointer b);
    compare_PEER_function = compare_PEER;
    GList* peerFind = g_list_find_custom_ts(connectedPeer, peerFake, compare_PEER_function, &connectedPeerLock);
    if(peerFind == NULL){
        return -1;
    }
    struct PEER* p = g_list_get_element_at_ts(connectedPeer, 0, &connectedPeerLock);
    p->load = peerLoad;
    p->ava = peerAva;
    gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
    compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
    newFile->completePossessorsPeer = g_list_append_ts(newFile->completePossessorsPeer, p, &newFile->completePossessorLock);
    int i;
    for(i=0;i<chunkNumber;i++){
        struct CHUNK_IN_SUPER_PEER* newChunk = calloc(1, sizeof(struct CHUNK_IN_SUPER_PEER));
        strcpy(newChunk->ID, chunkArrayPtr->ID);
        newChunk->size = chunkArrayPtr->size;
        newChunk->partialPossessorsPeers = NULL;
        g_static_rw_lock_init(&newChunk->partialPossessorLock);
        newFile->chunkInSuperPeer = g_list_append_ts(newFile->chunkInSuperPeer, newChunk, &newFile->chunkListLock);
        chunkArrayPtr++;
    }
    fileRepresentation = g_list_insert_sorted_ts(fileRepresentation, newFile, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
    //stampo il contenuto della lista dei File
    for(i=0;i<(int)g_list_length_ts(fileRepresentation, &fileRepresentationLock);i++){
        struct FILE_IN_SUPER_PEER * fisp = g_list_get_element_at_ts(fileRepresentation, i, &fileRepresentationLock);
        printf("ID del file: %s\n",fisp->ID);
    }
    free(peerFake);
    return 0;
}

int insertNewPeerInCompletePossessorsList(char* hashFile, struct PEER* newPeer){
    struct FILE_IN_SUPER_PEER* fispFake = calloc(1, sizeof(struct FILE_IN_SUPER_PEER));
    strcpy(fispFake->ID, hashFile);
    gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
    compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
    GList* fileFind = g_list_find_custom_ts(fileRepresentation, fispFake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
    if(fileFind == NULL){
        return -1;
    }
    struct FILE_IN_SUPER_PEER* file = (struct FILE_IN_SUPER_PEER*) g_list_nth_data(fileFind,0);
    file->completePossessorsPeer = g_list_append_ts(file->completePossessorsPeer, newPeer, &file->completePossessorLock);
    free(fispFake);
    return 0;
}

int insertNewChunkInPossessorsList(char* hashFile, char* hashChunk, struct PEER* newPeer){
    struct FILE_IN_SUPER_PEER* fispFake = calloc(1, sizeof(struct FILE_IN_SUPER_PEER));
    struct CHUNK_IN_SUPER_PEER* cispFake = calloc(1, sizeof(struct CHUNK_IN_SUPER_PEER));
    strcpy(fispFake->ID, hashFile);
    gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
    compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
    gint(*compare_CHUNK_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
    compare_CHUNK_IN_SUPER_PEER_function = compare_CHUNK_IN_SUPER_PEER;
    GList* fileFind = g_list_find_custom_ts(fileRepresentation, fispFake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
    if(fileFind == NULL){
        return -1;
    }
    free(fispFake);
    struct FILE_IN_SUPER_PEER* file = (struct FILE_IN_SUPER_PEER*) g_list_nth_data(fileFind,0);
    GList* chunkFind = g_list_find_custom_ts(file->chunkInSuperPeer, cispFake, compare_CHUNK_IN_SUPER_PEER_function, &file->chunkListLock);
    if(chunkFind == NULL){
        return -2;
    }
    free(cispFake);
    struct CHUNK_IN_SUPER_PEER* ch = (struct CHUNK_IN_SUPER_PEER*) g_list_nth_data(chunkFind, 0);
    ch->partialPossessorsPeers = g_list_append_ts(ch->partialPossessorsPeers, newPeer, &ch->partialPossessorLock);
    return 0;

}

int removeFile(char* hashFile)
{
    struct FILE_IN_SUPER_PEER* fispFake = calloc(1, sizeof(struct FILE_IN_SUPER_PEER));
    strcpy(fispFake->ID, hashFile);
    gint(*compare_FILE_IN_SUPER_PEER_function) (gconstpointer a, gconstpointer b);
    compare_FILE_IN_SUPER_PEER_function = compare_FILE_IN_SUPER_PEER;
    //cerco prima il file
    GList *temp = g_list_remove_custom_ts(fileRepresentation, fispFake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
    if(temp!=NULL){
        fileRepresentation = g_list_remove_custom_ts(fileRepresentation, fispFake, compare_FILE_IN_SUPER_PEER_function, &fileRepresentationLock);
        fispFake = (struct FILE_IN_SUPER_PEER *)g_list_nth_data(temp, 0);
        free(fispFake);
    }
        
}



